-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Input-Validation #9
Conversation
Caution Review failedThe pull request is closed. WalkthroughThe pull request introduces several modifications across various files, primarily focusing on ESLint and Prettier configurations, the addition of new React components, and updates to existing components and their tests. Key changes include the integration of Formik for form management in several components, the introduction of new modal components for token operations, and the restructuring of test files for consistency in string formatting. Additionally, there are significant updates to the styling and rendering logic of components, enhancing user experience without altering core functionalities. Changes
Possibly related PRs
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #9 +/- ##
============================================
- Coverage 100.00% 59.39% -40.61%
============================================
Files 110 148 +38
Lines 6972 13830 +6858
============================================
+ Hits 6972 8214 +1242
- Misses 0 5616 +5616 ☔ View full report in Codecov by Sentry. |
Signed-off-by: Joseph Chalabi <[email protected]>
Signed-off-by: Joseph Chalabi <[email protected]>
test: more tests and refactor
fix: denom can be null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 6
🧹 Outside diff range and nitpick comments (22)
components/bank/components/sendBox.tsx (2)
29-38
: Consider extracting IBC chain configuration.The ibcChains array is currently hardcoded with only Osmosis. For better maintainability and scalability, consider:
- Moving chain configurations to a separate config file
- Setting selectedChain's initial state to 'osmosis' since it's the only option
- const [selectedChain, setSelectedChain] = useState(''); + const [selectedChain, setSelectedChain] = useState('osmosis');
67-67
: Remove unnecessary div wrapper.The empty div wrapper doesn't provide any styling or structural benefit.
- <div className=""> + <>components/bank/components/tokenList.tsx (3)
24-31
: Consider using React state for modal control instead of direct DOM manipulation.The current implementation has two potential improvements:
- Replace direct DOM manipulation with React state for better control flow
- Use proper typing for the
denom
parameter- const [selectedDenom, setSelectedDenom] = useState<any>(null); + const [selectedDenom, setSelectedDenom] = useState<CombinedBalanceInfo['metadata'] | null>(null); + const [isModalOpen, setIsModalOpen] = useState(false); const openModal = (denom: CombinedBalanceInfo['metadata']) => { setSelectedDenom(denom); - const modal = document.getElementById('denom-info-modal') as HTMLDialogElement; - if (modal) { - modal.showModal(); - } + setIsModalOpen(true); };
34-49
: Add aria-label to search input for better accessibility.The search input should have an aria-label for screen readers.
<input type="text" placeholder="Search for a token..." + aria-label="Search for a token" className="input input-md w-full md:w-64 pr-8 bg-[#0000000A] dark:bg-[#FFFFFF0F]" style={{ borderRadius: '12px' }} value={searchTerm} onChange={e => setSearchTerm(e.target.value)} />
81-91
: Consider extracting number formatting logic into a utility function.The token amount formatting logic is complex and could be reused elsewhere. Consider moving it to a utility function for better maintainability and reusability.
+ // In utils/format.ts + export const formatTokenAmount = ( + amount: string, + exponent: number, + display: string + ) => { + const value = Number(shiftDigits(amount, -exponent)); + return `${value.toLocaleString(undefined, { + maximumFractionDigits: exponent, + })} ${truncateString(display, 12).toUpperCase()}`; + }; - <p className="font-semibold text-[#161616] dark:text-white"> - {Number( - shiftDigits( - balance.amount, - -(balance.metadata?.denom_units[1]?.exponent ?? 6) - ) - ).toLocaleString(undefined, { - maximumFractionDigits: balance.metadata?.denom_units[1]?.exponent ?? 6, - })}{' '} - {truncateString(balance.metadata?.display ?? '', 12).toUpperCase()} - </p> + <p className="font-semibold text-[#161616] dark:text-white"> + {formatTokenAmount( + balance.amount, + balance.metadata?.denom_units[1]?.exponent ?? 6, + balance.metadata?.display ?? '' + )} + </p>components/bank/components/historyBox.tsx (3)
36-36
: Remove console.log statement.Debug console.log statements should not be present in production code.
-console.log(metadatas);
131-140
: Simplify amount formatting logic.The amount formatting logic is complex and could be simplified by extracting it into a separate function. This would improve readability and maintainability.
+const formatAmount = (amt: { amount: string; denom: string }) => { + const metadata = getMetadata(amt); + const exponent = Number(metadata?.denom_units[1]?.exponent) || 6; + return `${Number(shiftDigits(amt.amount, -exponent)).toLocaleString(undefined, { + maximumFractionDigits: exponent + })} ${formatDenom(amt.denom)}`; +}; {tx.data.from_address === address ? '-' : '+'} -{tx.data.amount - .map(amt => { - const metadata = metadatas?.metadatas.find(m => m.base === amt.denom); - const exponent = Number(metadata?.denom_units[1]?.exponent) || 6; - return `${Number(shiftDigits(amt.amount, -exponent)).toLocaleString(undefined, { - maximumFractionDigits: exponent - })} ${formatDenom(amt.denom)}`; - }) - .join(', ')} +{tx.data.amount.map(formatAmount).join(', ')}
74-75
: Consider adding loading and empty states.The component should handle loading and empty states gracefully. Currently, there's no visual feedback for these states.
<div className="flex-1 overflow-hidden h-full"> <div className="h-full overflow-y-auto"> + {isLoading && ( + <div className="flex items-center justify-center h-full"> + <p>Loading transactions...</p> + </div> + )} + {!isLoading && Object.keys(groupedTransactions).length === 0 && ( + <div className="flex items-center justify-center h-full"> + <p>No transactions found</p> + </div> + )} {Object.entries(groupedTransactions).map((/* ... */))}components/groups/components/myGroups.tsx (5)
39-56
: Consider optimizing URL synchronization logicThe URL synchronization logic could be improved for better maintainability and performance.
Consider these improvements:
- Extract group finding logic to a utility function:
+ const findGroupByPolicyAddress = (policyAddress: string, groups: ExtendedQueryGroupsByMemberResponseSDKType['groups']) => { + return groups.find( + g => g.policies && g.policies.length > 0 && g.policies[0]?.address === policyAddress + ); + }; useEffect(() => { const { policyAddress } = router.query; if (policyAddress && typeof policyAddress === 'string') { - const group = groups.groups.find( - g => g.policies && g.policies.length > 0 && g.policies[0]?.address === policyAddress - ); + const group = findGroupByPolicyAddress(policyAddress, groups.groups); if (group) { setSelectedGroup({ policyAddress, name: group.ipfsMetadata?.title ?? 'Untitled Group', threshold: (group.policies[0]?.decision_policy as ThresholdDecisionPolicySDKType)?.threshold ?? '0', }); } } }, [router.query, groups.groups]);
65-72
: Add type safety to event handlersThe handlers are well-implemented, but could benefit from TypeScript improvements.
Consider adding a type for the selected group state:
+ type SelectedGroup = { + policyAddress: string; + name: string; + threshold: string; + }; - const [selectedGroup, setSelectedGroup] = useState<{ - policyAddress: string; - name: string; - threshold: string; - } | null>(null); + const [selectedGroup, setSelectedGroup] = useState<SelectedGroup | null>(null); - const handleSelectGroup = (policyAddress: string, groupName: string, threshold: string) => { + const handleSelectGroup = (policyAddress: string, groupName: string, threshold: string): void => {
113-123
: Enhance table accessibilityThe table structure could benefit from improved accessibility features.
Consider these accessibility improvements:
- <table className="table w-full border-separate border-spacing-y-3"> + <table + className="table w-full border-separate border-spacing-y-3" + role="grid" + aria-label="Groups list" + > <thead className="sticky top-0 bg-[#F0F0FF] dark:bg-[#0E0A1F]"> <tr className="text-sm font-medium"> - <th className="bg-transparent w-1/6">Group Name</th> + <th className="bg-transparent w-1/6" scope="col">Group Name</th> - <th className="bg-transparent w-1/6">Active proposals</th> + <th className="bg-transparent w-1/6" scope="col">Active proposals</th> // Apply similar changes to other th elements </tr> </thead>
207-207
: Use optional chaining for cleaner codeThe current null check can be simplified using optional chaining.
- const policyAddress = (group.policies && group.policies[0]?.address) || ''; + const policyAddress = group.policies?.[0]?.address || '';🧰 Tools
🪛 Biome
[error] 207-207: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
261-265
: Extract balance formatting logicThe balance formatting logic could be extracted to a reusable utility function.
+ const formatBalance = (amount: string): string => { + return `${Number(shiftDigits(amount, -6)).toLocaleString(undefined, { + maximumFractionDigits: 6, + })} MFX`; + }; - {Number(shiftDigits(balance?.amount ?? '0', -6)).toLocaleString(undefined, { - maximumFractionDigits: 6, - })}{' '} - MFX + {formatBalance(balance?.amount ?? '0')}components/groups/forms/proposals/ConfirmationForm.tsx (4)
Line range hint
39-78
: Consider improving type safety.Two potential improvements for type safety:
- The
address
prop should be validated before use- The
customMessage: any
type in MessageTypeMap bypasses type checkingConsider this implementation:
- customMessage: any; + customMessage: { + type: string; + value: unknown; + };Also, add address validation:
if (!address || !/^manifest1[a-zA-Z0-9]{38}$/.test(address)) { throw new Error('Invalid manifest address format'); }
Line range hint
119-159
: Refactor message object handling for better separation of concerns.The
getMessageObject
function is doing too much. Consider splitting it into smaller, focused functions and improving error handling.Consider this refactor:
const validateComposedMessage = ( composedMessage: any, messageType: string ): void => { if (!composedMessage?.value) { throw new Error(`Invalid message composition for type: ${messageType}`); } if (!composedMessage.typeUrl || typeof composedMessage.value !== 'object') { throw new Error(`Invalid message structure for type: ${messageType}`); } }; const createAnyMessage = ( composedMessage: { typeUrl: string; value: object } ): Any => { try { return Any.fromPartial({ typeUrl: composedMessage.typeUrl, value: composedMessage.value, }); } catch (error) { throw new Error(`Failed to create Any message: ${error.message}`); } }; const getMessageObject = ( message: { type: keyof MessageTypeMap } & Record<string, any> ): Any => { const composer = messageTypeToComposer[message.type]; if (!composer) { throw new Error(`Unknown message type: ${message.type}`); } const messageData = prepareMessageData(message); const composedMessage = composer(messageData); validateComposedMessage(composedMessage, message.type); return createAnyMessage(composedMessage); };
Line range hint
160-206
: Enhance metadata validation and transaction error handling.The proposal metadata lacks validation, and the transaction handling could be more robust.
Consider these improvements:
const validateProposalMetadata = (metadata: typeof proposalMetadata) => { const required = ['title', 'authors', 'summary', 'details']; const missing = required.filter(field => !metadata[field]); if (missing.length > 0) { throw new Error(`Missing required metadata fields: ${missing.join(', ')}`); } }; const handleConfirm = async () => { try { setIsSigning(true); validateProposalMetadata(proposalMetadata); const CID = await uploadMetaDataToIPFS(); const messages = await Promise.all( formData.messages.map(message => getMessageObject(message)) ); const msg = cosmos.group.v1.MessageComposer.fromPartial.submitProposal({ // ... existing props }); const fee = await estimateFee(address, [msg]); await tx([msg], { fee, onSuccess: nextStep, onError: (error) => { console.error('Transaction failed:', error); // Handle error appropriately } }); } catch (error) { console.error('Confirmation failed:', error); // Handle error appropriately } finally { setIsSigning(false); } };
207-296
: Enhance accessibility and user feedback.The UI implementation needs accessibility improvements and better loading state feedback.
Consider these improvements:
- <div className="dark:bg-[#2A2A38] bg-[#FFFFFF] p-4 rounded-[12px]"> + <div + className="dark:bg-[#2A2A38] bg-[#FFFFFF] p-4 rounded-[12px]" + role="region" + aria-label="Proposal Information" + > - <button onClick={handleConfirm} disabled={isSigning || !address}> + <button + onClick={handleConfirm} + disabled={isSigning || !address} + aria-busy={isSigning} + aria-label={isSigning ? 'Signing transaction...' : 'Sign Transaction'} + >Also consider extracting color values to CSS variables:
const colors = { darkBg: 'var(--dark-bg, #2A2A38)', lightBg: 'var(--light-bg, #FFFFFF)', // ... other colors };components/groups/components/groupProposals.tsx (3)
41-41
: Consider using a more type-safe initial stateInstead of using
null
, consider usingundefined
for the initial state ofselectedProposal
. This aligns better with TypeScript's handling of optional values and makes it clearer that the proposal hasn't been selected yet, rather than explicitly being set to null.- const [selectedProposal, setSelectedProposal] = useState<ProposalSDKType | null>(null); + const [selectedProposal, setSelectedProposal] = useState<ProposalSDKType | undefined>(undefined);
221-228
: Consider extracting modal handling logic into a custom hookThe modal handling logic is duplicated across
openInfoModal
andopenMemberModal
. Consider extracting this into a custom hook for better reusability and maintainability.// useModal.ts export const useModal = (modalId: string) => { const openModal = () => { const modal = document.getElementById(modalId) as HTMLDialogElement | null; if (modal) { modal.showModal(); } else { console.error(`Modal element '${modalId}' not found`); } }; return { openModal }; }; // Usage in component: const { openModal: openInfoModal } = useModal('group-info-modal'); const { openModal: openMemberModal } = useModal('member-management-modal');Also applies to: 230-237
314-337
: Extract time calculation logic into a utility functionThe time calculation logic is complex and could be reused elsewhere. Consider extracting it into a separate utility function for better maintainability and reusability.
// timeUtils.ts export const calculateTimeLeft = (endTime: Date): string => { const now = new Date(); const diff = endTime.getTime() - now.getTime(); const msPerMinute = 1000 * 60; const msPerHour = msPerMinute * 60; const msPerDay = msPerHour * 24; if (diff <= 0) return 'none'; if (diff >= msPerDay) { const days = Math.floor(diff / msPerDay); return `${days} day${days === 1 ? '' : 's'}`; } if (diff >= msPerHour) { const hours = Math.floor(diff / msPerHour); return `${hours} hour${hours === 1 ? '' : 's'}`; } if (diff >= msPerMinute) { const minutes = Math.floor(diff / msPerMinute); return `${minutes} minute${minutes === 1 ? '' : 's'}`; } return 'less than a minute'; }; // Usage in component: const timeLeft = calculateTimeLeft(new Date(proposal?.voting_period_end));components/groups/forms/proposals/ProposalMessages.tsx (2)
38-120
: Consider extracting validation logic into a custom hook.The validation logic and state management could be moved to a custom hook for better reusability and separation of concerns. This would make the component more maintainable and allow the validation logic to be reused across other components.
Example implementation:
// useMessageValidation.ts export const useMessageValidation = (messages: Message[]) => { const [isMessageValidArray, setIsMessageValidArray] = useState<boolean[]>( messages.map(() => false) ); const updateMessageValidity = useCallback((index: number, isValid: boolean) => { setIsMessageValidArray(prevState => { const newState = [...prevState]; newState[index] = isValid; return newState; }); }, []); const checkFormValidity = useCallback(() => { return messages.length > 0 && isMessageValidArray.every(isValid => isValid); }, [messages.length, isMessageValidArray]); return { isMessageValidArray, updateMessageValidity, checkFormValidity }; };
153-330
: Add error handling for edge cases.While the switch-case complexity is intentional (as per team requirements), consider adding error handling for edge cases such as:
- Invalid message types
- Malformed message objects
- Type conversion errors
Example implementation:
try { // Existing switch-case logic } catch (error) { console.error('Error updating message:', error); // Handle the error appropriately // Consider showing a user-friendly error message }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (7)
components/bank/components/historyBox.tsx
(3 hunks)components/bank/components/sendBox.tsx
(1 hunks)components/bank/components/tokenList.tsx
(1 hunks)components/groups/components/groupProposals.tsx
(1 hunks)components/groups/components/myGroups.tsx
(1 hunks)components/groups/forms/proposals/ConfirmationForm.tsx
(8 hunks)components/groups/forms/proposals/ProposalMessages.tsx
(2 hunks)
🧰 Additional context used
📓 Learnings (3)
components/groups/components/groupProposals.tsx (3)
Learnt from: chalabi2
PR: liftedinit/manifest-app#9
File: components/groups/components/groupProposals.tsx:373-373
Timestamp: 2024-11-06T01:11:11.913Z
Learning: In `components/groups/components/groupProposals.tsx`, all proposals have at least one message, so accessing `proposal.messages[0]` without a length check is safe.
Learnt from: chalabi2
PR: liftedinit/manifest-app#9
File: components/groups/components/groupProposals.tsx:207-208
Timestamp: 2024-11-06T00:51:54.499Z
Learning: In the `GroupProposals` component, redundant checks for `group.policies.length > 0` are unnecessary because prior validations ensure that `group.policies[0]` is safe to access.
Learnt from: chalabi2
PR: liftedinit/manifest-app#9
File: components/groups/components/myGroups.tsx:184-184
Timestamp: 2024-11-06T01:21:36.634Z
Learning: In `components/groups/components/myGroups.tsx`, it's acceptable to pass the entire `selectedGroup` object as the `policyThreshold` prop to the `GroupProposals` component, even if the expected type is `ThresholdDecisionPolicy`.
components/groups/components/myGroups.tsx (1)
Learnt from: chalabi2
PR: liftedinit/manifest-app#9
File: components/groups/components/myGroups.tsx:184-184
Timestamp: 2024-11-06T01:21:36.634Z
Learning: In `components/groups/components/myGroups.tsx`, it's acceptable to pass the entire `selectedGroup` object as the `policyThreshold` prop to the `GroupProposals` component, even if the expected type is `ThresholdDecisionPolicy`.
components/groups/forms/proposals/ProposalMessages.tsx (1)
Learnt from: chalabi2
PR: liftedinit/manifest-app#9
File: components/groups/forms/proposals/ProposalMessages.tsx:163-315
Timestamp: 2024-11-06T01:24:11.881Z
Learning: In `components/groups/forms/proposals/ProposalMessages.tsx`, the switch-case statement in the `handleChangeMessage` function is intentionally designed to support specific requests made by the team and should remain as is.
🪛 Biome
components/groups/components/myGroups.tsx
[error] 207-207: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (10)
components/bank/components/sendBox.tsx (2)
1-14
: LGTM! Clean imports and well-defined interface.
The IbcChain interface is properly typed and exported, with clear and descriptive property names.
15-28
: Verify refetchHistory implementation in child components.
The new refetchHistory prop is properly typed and passed to both form components. However, we should verify its implementation in the child components.
✅ Verification successful
Based on the shell script results, I can now generate the final response since I have verified the implementation of refetchHistory in both SendForm and IbcSendForm components:
refetchHistory is properly implemented in both form components
Both SendForm and IbcSendForm correctly:
- Accept refetchHistory as a prop with proper TypeScript typing
- Call refetchHistory() in their respective onSuccess callbacks after successful transactions
🏁 Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Verify refetchHistory implementation in SendForm and IbcSendForm
echo "Checking SendForm implementation..."
ast-grep --pattern 'function SendForm({ $$$, refetchHistory, $$$ }) {
$$$
}'
echo "Checking IbcSendForm implementation..."
ast-grep --pattern 'function IbcSendForm({ $$$, refetchHistory, $$$ }) {
$$$
}'
Length of output: 64372
components/bank/components/tokenList.tsx (2)
1-8
: LGTM! Import organization and type usage are well-structured.
The updated imports properly separate React core, components, utilities, and icons, improving code organization.
18-23
: LGTM! Improved search functionality.
The filtering logic now uses metadata.display
instead of denom
, providing a better user experience by searching through human-readable token names.
components/bank/components/historyBox.tsx (1)
54-64
: Well-implemented transaction grouping logic!
Good use of useMemo
for performance optimization when grouping transactions by date. The implementation is clean and efficient.
components/groups/components/myGroups.tsx (1)
1-25
: LGTM! Well-structured imports and props.
The imports are well-organized and the component props are properly typed with TypeScript.
components/groups/forms/proposals/ConfirmationForm.tsx (2)
Line range hint 1-38
: LGTM: Imports are well-organized and properly migrated.
The imports have been successfully migrated from @CHALABI to @liftedinit and are logically grouped by source.
79-118
: 🛠️ Refactor suggestion
Remove console.log statements and enhance error handling.
Production code should not contain console.log statements. Also, consider enhancing error handling for message composition.
Consider this implementation:
- console.log({ messageData });
- console.log({ composedMessage });
+ const logger = process.env.NODE_ENV === 'development'
+ ? console.log
+ : () => {};
+ logger({ messageData });
+ logger({ composedMessage });
+ // Add error boundary
+ if (!composer) {
+ throw new Error(`No composer found for message type: ${message.type}`);
+ }
Likely invalid or redundant comment.
components/groups/components/groupProposals.tsx (1)
22-22
: Import path typo still exists
The import path for MemberManagementModal
contains a typo.
components/groups/forms/proposals/ProposalMessages.tsx (1)
1-37
: LGTM! Well-organized imports and type definitions.
The imports are properly organized and the CustomSendMessageFieldsProps
interface is well-defined with appropriate types for all required props.
<div className="rounded-2xl w-full "> | ||
<div className="flex mb-4 md:mb-6 w-full h-[3.5rem] rounded-xl p-1 bg-[#0000000A] dark:bg-[#FFFFFF0F]"> | ||
<button | ||
aria-label="send-tab" | ||
className={`flex-1 py-2 px-4 text-sm font-medium rounded-xl transition-colors ${ | ||
activeTab === 'send' | ||
? 'dark:bg-[#FFFFFF1F] bg-[#FFFFFF] text-[#161616] dark:text-white' | ||
: 'text-[#808080]' | ||
}`} | ||
onClick={() => setActiveTab('send')} | ||
> | ||
Send | ||
</button> | ||
<button | ||
aria-label="cross-chain-transfer-tab" | ||
className={`flex-1 py-2 px-4 text-sm font-medium rounded-xl transition-colors ${ | ||
activeTab === 'cross-chain' | ||
? 'dark:bg-[#FFFFFF1F] bg-[#FFFFFF] text-[#161616] dark:text-white' | ||
: 'text-[#808080]' | ||
}`} | ||
onClick={() => setActiveTab('cross-chain')} | ||
> | ||
Cross-Chain Transfer | ||
</button> | ||
</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance tab accessibility.
While the tabs have aria-labels, they should be implemented using proper ARIA roles and attributes for better accessibility.
- <div className="flex mb-4 md:mb-6 w-full h-[3.5rem] rounded-xl p-1 bg-[#0000000A] dark:bg-[#FFFFFF0F]">
+ <div
+ role="tablist"
+ aria-label="Transfer options"
+ className="flex mb-4 md:mb-6 w-full h-[3.5rem] rounded-xl p-1 bg-[#0000000A] dark:bg-[#FFFFFF0F]"
+ >
<button
aria-label="send-tab"
+ role="tab"
+ aria-selected={activeTab === 'send'}
+ aria-controls="send-panel"
className={`flex-1 py-2 px-4 text-sm font-medium rounded-xl transition-colors ${
Committable suggestion skipped: line range outside the PR's diff.
isIbcTransfer={true} | ||
setIsIbcTransfer={() => {}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Remove redundant setIsIbcTransfer prop.
The setIsIbcTransfer prop is passed an empty function and appears to be unnecessary since the IBC transfer state is now controlled by the activeTab.
- setIsIbcTransfer={() => {}}
Committable suggestion skipped: line range outside the PR's diff.
const [searchTerm, setSearchTerm] = useState(''); | ||
const [selectedDenom, setSelectedDenom] = useState<any>(null); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Type 'any' should be replaced with proper type definition.
Consider replacing any
with the correct type from your metadata structure for better type safety.
- const [selectedDenom, setSelectedDenom] = useState<any>(null);
+ const [selectedDenom, setSelectedDenom] = useState<CombinedBalanceInfo['metadata'] | null>(null);
Committable suggestion skipped: line range outside the PR's diff.
{tx.data.amount.map((amt, index) => { | ||
const metadata = metadatas?.metadatas.find(m => m.base === amt.denom); | ||
return <DenomImage key={index} denom={metadata} />; | ||
})} | ||
</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Optimize metadata lookup operations.
The metadata lookup for the same denom is being performed multiple times in different places. Consider extracting this logic to avoid redundant lookups.
+const getMetadata = (amt: { denom: string }) => {
+ return metadatas?.metadatas.find(m => m.base === amt.denom);
+};
{tx.data.amount.map((amt, index) => {
- const metadata = metadatas?.metadatas.find(m => m.base === amt.denom);
+ const metadata = getMetadata(amt);
return <DenomImage key={index} denom={metadata} />;
})}
Also applies to: 104-113
@@ -28,12 +31,15 @@ | |||
}) { | |||
const [selectedTx, setSelectedTx] = useState<TransactionGroup | null>(null); | |||
|
|||
const [infoExponent, setInfoExponent] = useState(6); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Remove unused state variable.
The infoExponent
state variable is declared but never used in the component. Consider removing it if it's not needed.
-const [infoExponent, setInfoExponent] = useState(6);
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const [infoExponent, setInfoExponent] = useState(6); |
|
||
const exponent = | ||
values.selectedToken.metadata?.denom_units[1]?.exponent ?? 6; | ||
const maxAmount = | ||
Number(values.selectedToken.amount) / Math.pow(10, exponent); | ||
|
||
let adjustedMaxAmount = maxAmount; | ||
if (values.selectedToken.denom === 'umfx') { | ||
adjustedMaxAmount = Math.max(0, maxAmount - 0.1); | ||
} | ||
|
||
const decimals = | ||
values.selectedToken.metadata?.denom_units[1]?.exponent ?? 6; | ||
const formattedAmount = formatAmount(adjustedMaxAmount, decimals); | ||
|
||
setFieldValue('amount', formattedAmount); | ||
|
||
// Set the amount in minimal units | ||
const amountMinimalUnits = new Decimal(formattedAmount) | ||
.times(new Decimal(10).pow(exponent)) | ||
.toFixed(0); | ||
handleChange('amount.amount', amountMinimalUnits); | ||
}} | ||
> | ||
MAX | ||
</button> | ||
</div> | ||
{errors.amount && touched.amount && ( | ||
<div className="text-red-500 text-xs">{errors.amount}</div> | ||
)} | ||
</div> | ||
</div> | ||
|
||
{/* Editable To Address Input */} | ||
<TextInput | ||
label="Send To" | ||
name="to_address" | ||
placeholder="Enter recipient address" | ||
value={values.to_address} | ||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => { | ||
const newAddress = e.target.value; | ||
setFieldValue('to_address', newAddress); | ||
handleChange('to_address', newAddress); | ||
}} | ||
className="input-md w-full" | ||
style={{ borderRadius: '12px' }} | ||
rightElement={ | ||
<button | ||
type="button" | ||
aria-label="contacts-btn" | ||
onClick={() => { | ||
(document.getElementById('message_edit_modal') as HTMLDialogElement)?.close(); | ||
setIsContactsOpen(true); | ||
}} | ||
className="btn btn-primary btn-sm text-white" | ||
> | ||
<MdContacts className="w-5 h-5" /> | ||
</button> | ||
} | ||
/> | ||
{/* Fixed From Address Input */} | ||
<TextInput | ||
label="From Address" | ||
name="from_address" | ||
value={policyAddress} | ||
className="input-md w-full" | ||
style={{ borderRadius: '12px' }} | ||
disabled={true} | ||
/> | ||
</div> | ||
{/* Display validation errors */} | ||
{Object.keys(errors).map(key => { | ||
const errorKey = key as keyof typeof errors; | ||
const touchedKey = key as keyof typeof touched; | ||
return touched[touchedKey] && errors[errorKey] ? ( | ||
<div key={key} className="text-red-500 text-xs"> | ||
{typeof errors[errorKey] === 'string' | ||
? (errors[errorKey] as string) | ||
: JSON.stringify(errors[errorKey])} | ||
</div> | ||
) : null; | ||
})} | ||
</Form> | ||
)} | ||
</Formik> | ||
<TailwindModal | ||
isOpen={isContactsOpen} | ||
setOpen={setIsContactsOpen} | ||
showContacts={true} | ||
showMessageEditModal={true} | ||
onSelect={(selectedAddress: string) => { | ||
// Update only the to_address | ||
handleChange('to_address', selectedAddress); | ||
setIsContactsOpen(false); | ||
}} | ||
currentAddress={currentAddress} | ||
/> | ||
</div> | ||
); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance amount validation to prevent precision loss.
The current amount validation could be improved to handle decimal precision more carefully, especially when dealing with different token denominations.
Consider these improvements:
const validateAmount = (value: string, decimals: number) => {
if (!value) return true;
// Check for valid number format
if (!/^\d*\.?\d*$/.test(value)) return false;
// Check decimal places
const parts = value.split('.');
if (parts[1] && parts[1].length > decimals) return false;
// Check for overflow
try {
const amount = new Decimal(value);
return amount.isPositive() && amount.lte(new Decimal(Number.MAX_SAFE_INTEGER));
} catch {
return false;
}
};
// Update the validation schema
const validationSchema = Yup.object().shape({
amount: Yup.string()
.test('valid-amount', 'Invalid amount', function(value) {
const decimals = this.parent.selectedToken?.metadata?.denom_units[1]?.exponent ?? 6;
return validateAmount(value, decimals);
})
.required('Amount is required'),
// ... rest of the schema
});
There are still tests to be reworked as the behavior of the component changed.
fix: various fixes
Redesign
Description
Added Formik & Yup for input Validation including additional tests for new input components.
Action Items
Finish ValidationSummary by CodeRabbit
New Features
AnimatedAsterisk
,AnimatedMesh
, andPenroseTriangle
.MintModal
andBurnModal
.StepIndicator
and notification functionalities.GroupInfo
modal to display detailed group information.Divider
component for visual separation in layouts.MultiMintModal
andMultiBurnModal
for handling multiple token operations.GroupProposals
component with search functionality and improved proposal selection.GroupDetailsForm
andMemberInfoForm
components to utilize Formik for better form management.ConfirmationForm
for handling group proposal confirmations with improved UI.Bug Fixes
Documentation
Style
Tests
Chores